package com.puyo;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import java.util.StringTokenizer;

/**
 * @author akash
 * Main Game class. Contains the logic for the game.
 */
public class PaintPane extends JPanel implements ActionListener {

	private static final long serialVersionUID = 1L;
	private static final int ARR_ROWS = 12;
	private static final int ARR_COLS = 6;
	private static final int IMAGES_COUNT = 4;
	private static final int TIMER1_START_VAL = 1100;
	private static final int TIMER2_START_VAL = 1000;
	private static final int TIMER3_START_VAL = 500;
	private static final int DELAY_MULTIPLY_FACTOR = 100;
	private static final int WIDTH = 100;
	private static final int HEIGHT = 30;
	private static final int X_VAL = 213;
	private static final int Y_VAL = 300;
	private static final int COMBO_COUNT = 4;
	private static final int MAX_SMILEYS_ERASED = 72;
	private static final int MARKS_PER_PUYO = 10;
	private static final int MAX_LEVEL = 10;

	private int smileys[][] = new int[ARR_ROWS][ARR_COLS]; // smileys array holds the information about smileys to display
	private int x1, y1, x2, y2, c1, c2; // Holds the moving smileys information
	private String s; // Formation of chain combo is checked using this string
	private Timer t1, t2, t3; // different timers used for placing delays
	private Image images[] = new Image[IMAGES_COUNT]; // holds 4 diff colored smileys
	private Random randNumber; // this object used to generate random numbers
	private int rotation; // used for the rotation of smileys
	private boolean reached; // is smileys reached the ground or not
	private int count; // count of puyos when formed a chain combo
	private static int level; // level of the game
	private int score, erasedSmileys;
	private MenuPane mp;
	private PuyoPuyoGame ppg;
	private int nextPuyo1 = 0, nextPuyo2 = 0;
	private boolean mouseOver = false;

	/**
	 * @param ppg
	 * Constructor for PaintPane
	 */
	public PaintPane(PuyoPuyoGame ppg) {
		this.ppg = ppg;
		newGame();
		randNumber = new Random();
		t1 = new Timer(TIMER1_START_VAL, this);
		t1.setInitialDelay(0);
		t2 = new Timer(TIMER2_START_VAL, this);
		t3 = new Timer(TIMER3_START_VAL, this);
		images[0] = new ImageIcon("images//puyo_blue.png").getImage();
		images[1] = new ImageIcon("images//puyo_green.png").getImage();
		images[2] = new ImageIcon("images//puyo_red.png").getImage();
		images[3] = new ImageIcon("images//puyo_yellow.png").getImage();
		startFall();
		startFall();
		setFocusable(true);
		addGameKeyListener();
		addGameMouseListener();
		addGameMouseMotionListener();
	}

	/**
	 * Adds MouseMotion Listener
	 */
	private void addGameMouseMotionListener() {
		addMouseMotionListener(new MouseMotionAdapter() {
			int x = X_VAL, y = Y_VAL, w = WIDTH, h = HEIGHT;

			public void mouseMoved(MouseEvent e) {
				if (e.getX() > x && e.getX() < x + w && e.getY() > y
						&& e.getY() < y + h) {
					mouseOver = true;
				}
				else {
					mouseOver = false;
				}
				repaint();
			}
		});
	}

	/**
	 * Adds Mouse listener
	 */
	private void addGameMouseListener() {
		addMouseListener(new MouseAdapter() {
			int x = X_VAL, y = Y_VAL, w = WIDTH, h = HEIGHT;

			public void mouseClicked(MouseEvent e) {
				if (e.getX() > x && e.getX() < x + w && e.getY() > y
						&& e.getY() < y + h) {
					pauseGame();
					showMenu(true);
				}
			}
		});
	}

	/**
	 * Adds key Listener
	 */
	private void addGameKeyListener() {
		addKeyListener(new KeyAdapter() {
			public void keyPressed(KeyEvent e) {
				if (e.getKeyCode() == KeyEvent.VK_ENTER) {
					setDelays();
				}
				if(!reached){
					if (e.getKeyCode() == DefineControls.KEY_LEFT) {
						moveLeft();
						repaint();
					} else if (e.getKeyCode() == DefineControls.KEY_RIGHT) {
						moveRight();
						repaint();
					} else if (e.getKeyCode() == DefineControls.KEY_UP) {
						rotatePuyo();
						repaint();
					} else if (e.getKeyCode() == DefineControls.KEY_DOWN) {
						moveDown();
						repaint();
				}
				} else if (e.getKeyCode() == DefineControls.KEY_DOWN) {
					moveDown();
					repaint();
				}
			}

			/**
			 * Moves the smileys one step left
			 */
			private void moveLeft() {
				if (y1 > 0 && y2 > 0) // move the smileys one step left
				{
					if (x1 == x2) {
						if (y1 > y2) {
							swap();
						}
						if (smileys[x1][y1 - 1] == 0) {
							smileys[x2][y2] = 0;
							smileys[x1][--y1] = c1;
							smileys[x2][--y2] = c2;
						}
					} else if (smileys[x1][y1 - 1] == 0
							&& smileys[x2][y2 - 1] == 0) {
						smileys[x1][y1] = 0;
						smileys[x2][y2] = 0;
						smileys[x1][--y1] = c1;
						smileys[x2][--y2] = c2;
					}
				}
			}

			/**
			 * Move the smileys one step right
			 */
			private void moveRight() {

				if (y1 < ARR_COLS-1 && y2 < ARR_COLS-1) // move the smileys one step right
				{
					if (x1 == x2) {
						if (y1 < y2) {
							swap();
						}
						if (smileys[x1][y1 + 1] == 0) {
							smileys[x2][y2] = 0;
							smileys[x1][++y1] = c1;
							smileys[x2][++y2] = c2;
						}
					} else if (smileys[x1][y1 + 1] == 0
							&& smileys[x2][y2 + 1] == 0) {
						smileys[x1][y1] = 0;
						smileys[x2][y2] = 0;
						smileys[x1][++y1] = c1;
						smileys[x2][++y2] = c2;
					}
				}
			}

			/**
			 * Moves the smileys one step down
			 */
			private void moveDown() {
				if (x1 < x2) {// move the smileys one step down
					swap();
				}
				if (x1 < ARR_ROWS-1) {
					if (smileys[x1 + 1][y1] == 0) {
						smileys[x1][y1] = 0;
						smileys[++x1][y1] = c1;
					} else {
						reached = true;
					}
				}
				if (x2 < ARR_ROWS-1) {
					if (smileys[x2 + 1][y2] == 0) {
						smileys[x2][y2] = 0;
						smileys[++x2][y2] = c2;
					} else {
						reached = true;
					}
				}
			}

			/**
			 * Rotates the puyos by 90 degrees
			 */
			private void rotatePuyo() {
				switch(rotation){
				case 1:
					if (x1 > x2) {
						swap();
					}
					if (y1 > 0 && smileys[x1][y1 - 1] == 0) {
						smileys[x2][y2] = 0;
						smileys[--x2][--y2] = c2;
						rotation = 2;
					} else if (y1 < ARR_COLS-1 && smileys[x1][y1 + 1] == 0) {
						smileys[x2][y2] = 0;
						smileys[x1][++y1] = c1;
						smileys[--x2][y2] = c2;
						rotation = 2;
					}
				break;

				case 2:
					if (y1 > y2) {
						swap();
					}
					if (x1 > 0) {
						smileys[x1][y1] = 0;
						smileys[--x1][++y1] = c1;
						rotation = 3;
					}
				break;

				case 3:
					if (x1 < x2) {
						swap();
					}
					if (y1 < ARR_COLS-1 && smileys[x1][y1 + 1] == 0) {
						smileys[x2][y2] = 0;
						smileys[++x2][++y2] = c2;
						rotation = 4;
					} else if (y1 > 0 && smileys[x1][y1 - 1] == 0) {
						smileys[x2][y2] = 0;
						smileys[x1][--y1] = c1;
						smileys[++x2][y2] = c2;
						rotation = 4;
					}

					break;

				case 4:
					if (y1 < y2) {
						swap();
					}
					if (x1 < ARR_ROWS-1 && smileys[x1 + 1][y1 - 1] == 0) {
						smileys[x1][y1] = 0;
						smileys[++x1][--y1] = c1;
						rotation = 1;
					}
					break;
				default:
					break;
				}
			}
		});
	}

	/**
	 * @param b
	 * Show the menu options
	 */
	public final void showMenu(boolean b) {
		mp = new MenuPane(b);
		mp.setPaintPane(this, ppg);
		mp.setVisible(true);
		ppg.setVisible(false);
	}

	/**
	 * Pauses the game
	 */
	public final void pauseGame() {
		t1.stop();
	}

	/**
	 * Resume the game from the paused mode
	 */
	public void resumeGame() {
		t1.start();
	}

	/**
	 * Start new game by resetting variables
	 */
	public final void newGame() // start new game by resetting variables
	{
		for (int i = 0; i < ARR_ROWS; i++) {
			for (int j = 0; j < ARR_COLS; j++) {
				smileys[i][j] = 0;
			}
		}
		rotation = 1;
		reached = true;
		count = 0;
		score = 0;
		erasedSmileys = 0;
	}

	/**
	 * @param l
	 * Sets the level to l
	 */
	public static void setLevel(int l) {
		level = l;
	}

	/**
	 * Sets the delays according to the level
	 */
	public final void setDelays() {
		t1.setDelay(TIMER1_START_VAL - level * DELAY_MULTIPLY_FACTOR);
		t1.start();
	}

	/**
	 * Starts the fall of new smileys
	 */
	public final void startFall() // Start the fall of new Smileys
	{// Checking Top of the game window is occupied by any smileys, if occupied
		// then game is over

		if (smileys[0][3] == 0 && smileys[1][3] == 0) {
			c1 = smileys[0][3] = nextPuyo1;
			c2 = smileys[1][3] = nextPuyo2;
			int r;
			while ((r = randNumber.nextInt(5)) == 0){};// select random color from 4 colors
			nextPuyo1 = r;
			while ((r = randNumber.nextInt(5)) == 0){};// select random color from 4 colors
			nextPuyo2 = r;
			x1 = 0;
			y1 = 3;
			x2 = 1;
			y2 = 3;
			rotation = 1;
			reached = false;
		} else { // game is over
			t1.stop();
			String s = JOptionPane.showInputDialog(null, "Enter Your Name",
					"Congratulations:", JOptionPane.PLAIN_MESSAGE);
			showMenu(false);
			if (s != null) {
				mp.addScore(s, score);
			}
		}
	}

	/**
	 * Swaps the order of the smileys
	 */
	public final void swap() {
		int temp = x1;
		x1 = x2;
		x2 = temp;
		temp = y1;
		y1 = y2;
		y2 = temp;
		temp = c1;
		c1 = c2;
		c2 = temp;
	}

	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == t1) { // If Event is generated by the t1 object
			boolean reach = true;
			if (x1 < x2) {// Move smileys one step down
				swap();
			}
			if (x1 < ARR_ROWS-1) {
				reach = smiley1Location(reach);

			}
			if (x2 < ARR_ROWS-1) {
				reach = smiley2Location(reach);
			}
			if (reach) {
				eraseSmileys(); // so smileys reached the ground so erase Smiley
			}// which form chain combos
		} // t2 and t3 are used for make delay b/w erasing puyos and filling
		// vacated places by smileys
		else if (e.getSource() == t2) {
			eraseSmileys();
		} else if (e.getSource() == t3) {
			matchedSmileys();
		}
		repaint();
	}

	/**
	 * Matches the smileys forming a combination and erases them
	 */
	private void matchedSmileys() {

		for (int i = ARR_ROWS-2; i >= 0; i--) {
			// vacated places formed by erased smileys are filled with the
			// other smileys
			for (int j = 0; j < ARR_COLS; j++) {
				if (smileys[i][j] > 0) {
					arrangeMatchedSmiley(i,j);
				}
			}
		}
		t3.stop();
	}

	/**
	 * @param i
	 * @param j
	 * any smiley exist below the current smiley up to the ground then move on to it else move on to the ground
	 */
	private void arrangeMatchedSmiley(int i,int j) {

		int k;
		for (k = i + 1; k <= ARR_ROWS-1; k++) {
			if (smileys[k][j] > 0) {
				// any smiley exist below the
				// current smiley up to the
				// ground then move on to it

				smileys[k - 1][j] = smileys[i][j];
				if (i != k - 1) {
					smileys[i][j] = 0;
				}
				break;
			} else if (k == ARR_ROWS-1) {
				// if no smiley exist below the
				// current smiley up to the
				// ground then move on to the
				// ground
				smileys[ARR_ROWS-1][j] = smileys[i][j];
				if (i != ARR_ROWS-1) {
					smileys[i][j] = 0;
				}
			}
		}

	}

	/**
	 * @param reach
	 * @return
	 * Indicates if smiley1 has reached the ground or not
	 */
	private boolean smiley1Location(boolean reach) {
		boolean reachL = reach;
		if (smileys[x1 + 1][y1] == 0) {
			smileys[x1][y1] = 0;
			smileys[++x1][y1] = c1;
			reachL = false;
		} else {
			reached = true;
		}
		return reachL;
	}

	/**
	 * @param reach
	 * @return
	 * Indicates if smiley2 has reached the ground or not
	 */
	private boolean smiley2Location(boolean reach) {
		boolean reachL = reach;
		if (smileys[x2 + 1][y2] == 0) {
			smileys[x2][y2] = 0;
			smileys[++x2][y2] = c2;
			reachL = false;
		} else {
			reached = true;
		}
		return reachL;
	}


	/**
	 * Erases the smiley if a combo chain is detected
	 */
	public void eraseSmileys() {
		boolean erased = false;
		for (int i = 0; i < ARR_ROWS; i++) {
			for (int j = 0; j < ARR_COLS; j++) {
				if (smileys[i][j] > 0) {// for all smiley
					count = 1;
					s = "";
					arrangeChainCombo(i, j);
					if (count >= COMBO_COUNT) // if chain combo form with 4 or more
						// smileys
					{
						changeErasedSmileys();

						erased = true;
					}
				}
			}
		}
		if (erased) {
			t1.stop();
			t2.start();
			t3.start();
			return;
		}
		t2.stop();
		if (!t1.isRunning()) {
			t1.start();
		}
		startFall();
	}

	/**
	 * Updates the score card and changes level if required
	 */
	private void changeErasedSmileys() {
		StringTokenizer st = new StringTokenizer(s);
		while (st.hasMoreTokens()) {
			smileys[Integer.parseInt(st.nextToken())][Integer
			                                          .parseInt(st.nextToken())] = 0;
		}
		erasedSmileys += count;
		if (erasedSmileys >= MAX_SMILEYS_ERASED) // Change the level of the game
			// depending on number of
			// removed puyos.
		{
			changeLevel();
		}
		score += MARKS_PER_PUYO * count;
	}

	/**
	 * Change the level and show congratulatory message if won
	 */
	private void changeLevel() {
		level += 1;
		if (level == MAX_LEVEL+1) {
			t2.stop();
			JOptionPane
			.showMessageDialog(
					null,
					"****************/////// Congratulations ///////**************",
					"Message:",
					JOptionPane.PLAIN_MESSAGE);
			newGame();
			setDelays();
			startFall();
			return;
		}
		setDelays();
		erasedSmileys = 0;

	}

	/**
	 * @param x
	 * @param y
	 * Check for arrangement Chain Combo
	 */
	public void arrangeChainCombo(int x, int y) {
		s = s + " " + x + " " + y;
		if (y < ARR_COLS-1 && smileys[x][y] == smileys[x][y + 1]
		                                         && !isAlreadyExists(x, y + 1)) { // check for the same color
			// smiley at the right side
			// of
			count++; // current smiley which is not already added to the chain
			// combo
			arrangeChainCombo(x, y + 1); // Then check this smiley can connected
			// to any same color smiley
		}
		if (x < ARR_ROWS-1 && smileys[x][y] == smileys[x + 1][y]
		                                              && !isAlreadyExists(x + 1, y)) {
			count++;
			arrangeChainCombo(x + 1, y);
		}
		if (y > 0 && smileys[x][y] == smileys[x][y - 1]
		                                         && !isAlreadyExists(x, y - 1)) {
			count++;
			arrangeChainCombo(x, y - 1);
		}
		if (x > 0 && smileys[x][y] == smileys[x - 1][y]
		                                             && !isAlreadyExists(x - 1, y)) {
			count++;
			arrangeChainCombo(x - 1, y);
		}
	}

	/**
	 * @param x
	 * @param y
	 * @return
	 * Checks if the smiley is already in the chain combo or not
	 */
	public boolean isAlreadyExists(int x, int y) { // is the smiley already in the
	// chain combo or not
		StringTokenizer st = new StringTokenizer(s);
		while (st.hasMoreTokens()) {
			int a = Integer.parseInt(st.nextToken());
			int b = Integer.parseInt(st.nextToken());
			if (x == a && y == b) {
				return true;
			}
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see javax.swing.JComponent#paint(java.awt.Graphics)
	 */
	public void paint(Graphics g) {
		g.setColor(Color.gray);
		g.fill3DRect(0, 0, getWidth(), getHeight(), false);
		g.setColor(Color.white);
		g.fill3DRect(10, 10, 193, 384, true); // background fill with black
		// color
		g.fill3DRect(213, 10, 36, 68, true);
		g.drawImage(images[nextPuyo1 - 1], 215, 12, 32, 32, null);
		g.drawImage(images[nextPuyo2 - 1], 215, 44, 32, 32, null);
		for (int i = 0; i < 12; i++) {
			for (int j = 0; j < 6; j++) {
				if (smileys[i][j] > 0) { // display all the smileys
					g.drawImage(images[smileys[i][j] - 1], j * 32 + 10,
							i * 32 + 10, 32, 32, null);
				}
			}
		}
		g.setFont(new Font("Times New Roman", Font.BOLD, 20));
		g.setColor(Color.white);
		g.fill3DRect(213, 150, 100, 30, true);
		g.fill3DRect(213, 180, 100, 30, true);
		g.setColor(Color.black);
		g.drawString("Score", 238, 170);
		g.setFont(new Font("Times New Roman", 0, 20));
		g.drawString("" + score, 220, 200);
		g.setColor(Color.white);
		g.fill3DRect(213, 220, 100, 30, true);
		g.fill3DRect(213, 250, 100, 30, true);
		g.setColor(Color.black);
		g.setFont(new Font("Times New Roman", Font.BOLD, 20));
		g.drawString("Speed", 235, 240);
		g.setFont(new Font("Times New Roman", 0, 20));
		g.drawString("" + level, 220, 270);
		if (mouseOver) {
			g.setColor(Color.yellow);
		}
		else {
			g.setColor(Color.white);
		}
		g.fill3DRect(213, 300, 100, 30, true);
		g.setColor(Color.black);
		g.setFont(new Font("Times New Roman", Font.BOLD, 20));
		g.drawString("Menu", 238, 320);
	}
}
